package com.open.httpauto;

import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.Mode;
import cn.hutool.crypto.Padding;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.symmetric.AES;
import com.alibaba.fastjson2.JSONObject;
import com.open.httpauto.http.Context;
import com.open.httpauto.http.RequestParameter;

import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 对参数进行排序
 */
public interface IDigestHandler {

    /**
     * md5 签名
     *
     * @param requestMap      请求参数映射
     * @param paramJsonObject 参数数据
     * @param context         配置信息
     * @return 签名密文
     */
    default String md5Sign(List<RequestParameter> requestMap, JSONObject paramJsonObject, Context context) {
        DigestEnum digestEnum = DigestEnum.getEnum(context.getDigest());
        if (DigestEnum.MD5 != digestEnum) {
            return null;
        }

        String signKey = StrUtil.isBlank(context.getSignKey()) ? null : context.getSignKey();
        String appKey = context.getAppKey();
        boolean emptySign = context.isEmptySign();

        //签名
        String signText = this.sort(requestMap, paramJsonObject, signKey, appKey, emptySign);
        String sign = SecureUtil.md5(signText);

        //转换大小写
        UpperLowerEnum upperLowerEnum = UpperLowerEnum.getEnum(context.getUpperLower());
        return null != upperLowerEnum
                ? upperLowerEnum.format(sign)
                : sign;
    }

    /**
     * 默认按照 ASCII 排序
     *
     * @param parameterList   参数集合
     * @param paramJsonObject
     * @param signKey         签名变量名
     * @param appKey          签名秘钥
     * @param emptySign       为空是否签名 true/false
     * @return 排序后明文
     */
    private String sort(List<RequestParameter> parameterList, JSONObject paramJsonObject, String signKey, String appKey, boolean emptySign) {

        Map<String, RequestParameter> map = parameterList.stream()
                .filter(requestParameter -> requestParameter.isSign())
                .collect(Collectors.toMap(RequestParameter::getParameter, Function.identity()));
        //转成 tree map 自动对key 排序
        TreeMap<String, RequestParameter> treeMap = new TreeMap<>(map);

        List<String> textList = new LinkedList<>();
        treeMap.forEach((key, requestParameter) -> {
            String value = (String) paramJsonObject.get(requestParameter.getParameter());
            if (Objects.isNull(value) && emptySign) {
                textList.add(key.concat("=").concat(StrUtil.EMPTY));
            } else if (!Objects.isNull(value)) {
                textList.add(key.concat("=").concat(value.toString()));
            }
        });

        String text = String.join("&", textList);

        if (StrUtil.isNotBlank(signKey)) {
            text = text.concat("&" + signKey + "=")
                    .concat(appKey);
        } else {
            text = text.concat(appKey);
        }

        return text;
    }

    /**
     * aes 加密
     *
     * @param data    请求参数映射
     * @param context 配置信息
     * @return 密文
     */
    default String aesEncrypt(String data, Context context) {
        DigestEnum digestEnum = DigestEnum.getEnum(context.getDigest());
        if (DigestEnum.AES != digestEnum) {
            return null;
        }

        //aes 模式
        Mode mode = this.getMode(context.getMode());
        //aes padding
        Padding padding = this.getPadding(context.getPadding());

        String key = context.getKey();
        String iv = context.getIv();

        AES aes = new AES(mode, padding, new SecretKeySpec(key.getBytes(), "AES"), new IvParameterSpec(iv.getBytes()));
        String result = aes.encryptBase64(data, StandardCharsets.UTF_8);

        return result;
    }

    /**
     * aes 解密
     *
     * @param data    请求参数映射
     * @param context 配置信息
     * @return 明文
     */
    default String aesDecode(String data, Context context) {
        //aes 模式
        Mode mode = this.getMode(context.getMode());
        //aes padding
        Padding padding = this.getPadding(context.getPadding());

        String key = context.getKey();
        String iv = context.getIv();

        AES aes = new AES(mode, padding, new SecretKeySpec(key.getBytes(), "AES"), new IvParameterSpec(iv.getBytes()));
        String body = aes.decryptStr(data, StandardCharsets.UTF_8);
        return body;
    }

    /**
     * 获取 aes mode
     */
    private Mode getMode(String modeStr) {
        Mode mode = null;
        if (Mode.NONE.name().equalsIgnoreCase(modeStr)) {
            mode = Mode.NONE;
        } else if (Mode.CBC.name().equalsIgnoreCase(modeStr)) {
            mode = Mode.CBC;
        } else if (Mode.CFB.name().equalsIgnoreCase(modeStr)) {
            mode = Mode.CFB;
        } else if (Mode.CTR.name().equalsIgnoreCase(modeStr)) {
            mode = Mode.CTR;
        } else if (Mode.CTS.name().equalsIgnoreCase(modeStr)) {
            mode = Mode.CTS;
        } else if (Mode.ECB.name().equalsIgnoreCase(modeStr)) {
            mode = Mode.ECB;
        } else if (Mode.OFB.name().equalsIgnoreCase(modeStr)) {
            mode = Mode.OFB;
        } else if (Mode.PCBC.name().equalsIgnoreCase(modeStr)) {
            mode = Mode.PCBC;
        }
        return mode;
    }

    /**
     * 获取 padding
     */
    private Padding getPadding(String paddingStr) {
        Padding padding = null;
        if (Padding.NoPadding.name().equalsIgnoreCase(paddingStr)) {
            padding = Padding.NoPadding;
        } else if (Padding.ZeroPadding.name().equalsIgnoreCase(paddingStr)) {
            padding = Padding.ZeroPadding;
        } else if (Padding.ISO10126Padding.name().equalsIgnoreCase(paddingStr)) {
            padding = Padding.ISO10126Padding;
        } else if (Padding.OAEPPadding.name().equalsIgnoreCase(paddingStr)) {
            padding = Padding.OAEPPadding;
        } else if (Padding.PKCS1Padding.name().equalsIgnoreCase(paddingStr)) {
            padding = Padding.PKCS1Padding;
        } else if (Padding.PKCS5Padding.name().equalsIgnoreCase(paddingStr)) {
            padding = Padding.PKCS5Padding;
        } else if (Padding.SSL3Padding.name().equalsIgnoreCase(paddingStr)) {
            padding = Padding.SSL3Padding;
        }
        return padding;
    }


}
